/* robot.cpp -- class implementations for robot stuff, code here handles robot parsing, modification, saving, loading, interpretation. much of the engine is here.
 * robot.cpp is part of Robodrome
 *
 * Copyright (c) 2009 Alex French
 * Robodrome is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * Robodrome is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "robot.h"
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
#include <cmath>
#include "world.h"
#include "save.pb.h"
#define OPERATOR_OFFSET 100
#define REGISTER_OFFSET 200
#define REGISTER_MAX 263
#define NUMBER_OFFSET 40000
#define TOKEN_ERR -1
#define REQUIRES(n) if (m_stack.size() < n) return ERR_STACK_UNDERFLOW

const int energy_values[] = { 40, 60, 100, 150 };
const int damage_values[] = { 30, 60, 100, 150 };
const int shield_values[] = { 0,  25, 50,  100 };
const int cpu_values[]    = { 5,  10, 15,  30 , 50 };

const std::string operators[] = { "+", "-", "*", "/", "=", "!", ">", "<", "abs", "and", "arccos", "arcsin", "arctan", "beep", "call", "chs", "cosine", "debug", "dist", "drop", "dropall", "dup", "flushint", "icon0", "icon1", "icon2", "icon3", "icon4", "icon5", "icon6", "icon7", "icon8", "icon9", "if", "ife", "ifg", "ifeg", "intoff", "inton", "jump", "max", "min", "mod", "nop", "not", "or", "print", "recall", "return", "roll", "rti", "setint", "setparam", "sine", "snd0", "snd1", "snd2", "snd3", "snd4", "snd5", "snd6", "snd7", "snd8", "snd9", "sto", "store", "sqrt", "swap", "sync", "tangent", "vstore", "vrecall", "xor", "" };
const std::string registers[] = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "aim", "bottom", "bullet", "channel", "chronon", "collision", "damage", "doppler", "energy", "fire", "friend", "history", "hellbore", "id", "kills", "left", "look", "mine", "missile", "movex", "movey", "nuke", "probe", "radar", "random", "range", "right", "robot", "scan", "shield", "signal", "speedx", "speedy", "stunner", "teammates", "top", "wall", "" };

Robot::Robot()
{
	m_name = "New Robot";
	m_ltable = "";
	m_src = "";
	m_energy_max = 2;
	m_damage_max = 2;
	m_shield_max = 2;
	m_cpu_max = 1;
	m_bullet_type = 1;
	m_missiles = false;
	m_tacnukes = false;
	m_hellbores = false;
	m_mines = false;
	m_stunners = false;
	m_probes = false;
	m_turret_type = 2;
	m_no_negative_energy = false;
	m_alive = true;
	m_killer = "";
	m_modified = false;

	this->init();
	m_initialized = false;
}

Robot::~Robot()
{
}

std::string Robot::compile(void)
{
	std::stringstream errbuf;

	if (this->parse_labels(&errbuf))
		return errbuf.str();

	if (this->full_parse(&errbuf))
		return errbuf.str();
	
	return "";
}

// build label table from code in src string
// concatenate errors to errbuf
// label definitions are removed from src so that it can be passed to the parser
int Robot::parse_labels(std::stringstream *errbuf)
{
	std::string tmp = m_src;
	tmp += '\n';
	std::string token = "";
	int line = 1, instructions = 0;
	bool comment = false;

	// clear label table
	labels.erase(labels.begin(), labels.end());

	for (unsigned int i = 0; i < tmp.size(); i++) {
		if (tmp.at(i) == '\n') { // keep track of line number for error reporting
			comment = false;
			line++;
		}
		if (tmp.at(i) == '#' || comment) {
			comment = true;
		} else if (token.size() > 22) { // 20 character label max, 22 including ':' and '\0'
			*errbuf << "Error: token '" << token << "-' on line " << line << " is too long" << std::endl;
			return 1;
		} else if (isascii(tmp.at(i)) && !isspace(tmp.at(i))) {
			token += tmp.at(i);
		} else if (token.size() > 0) {
			if (token.at(token.size() - 1) == ':') {
				if (this->addlabel(token.substr(0, token.size() - 1), instructions, line)) {
					*errbuf << "Error: label '" << token.substr(0, token.size() - 1) << "' on line " << line << " is already defined on line " << labels[this->getlabel(token.substr(0, token.size() - 1))].m_src_line << ".";
					return 1;
				}
			} else {
				int n = ptoken(token);
				if (n >= REGISTER_OFFSET && n < REGISTER_MAX && token.at(token.size() - 1) != '\'') // token is an unquoted register and thus has a preceding recall instruction
					instructions++;
				instructions++;
			}
			token.clear();
		}
	}
	return 0;
}

int Robot::full_parse(std::stringstream *errbuf)
{
	std::string tmp = m_src;
	tmp += '\n';
	m_compiled.clear();
	std::string token = "";
	int line = 1, instructions = 0;
	bool comment;

	for (unsigned int i = 0; i < tmp.size(); i++) {
		if (tmp.at(i) == '\n') { // keep track of line number for error reporting
			comment = false;
			line++;
		}
		if (tmp.at(i) == '#' || comment) {
			comment = true;
		} else if (isascii(tmp.at(i)) && !isspace(tmp.at(i))) {
			token += tmp.at(i);
		} else if (token.size() > 0) {
			if (token.at(token.size() - 1) != ':') { // ignore label definitions
				int n = ptoken(token);
				if (n >= REGISTER_OFFSET && n < REGISTER_MAX && token.at(token.size() - 1) != '\'') { // account for unquoted variables
					m_compiled.push_back(n);
					m_compiled.push_back(ptoken("recall"));
					instructions += 2;
				} else if (n == -1) {
					*errbuf << "Error: unknown token '" << token << "' on line " << line << std::endl;
					return 1;
				} else {
					m_compiled.push_back(n);
					instructions++;
				}
			}
			token.clear();
		}
	}
	return 0;
}

int Robot::ptoken(std::string token)
{
	int n;

	// remove quote from token if present
	if (token.at(token.size() - 1) == '\'')
		token = token.substr(0, token.size() - 1);

	// operator?
	for (int i = 0; !operators[i].empty(); i++) {
		if (!token.compare(operators[i]))
			return i+OPERATOR_OFFSET;
	}

	// register?
	for (int i = 0; !registers[i].empty(); i++) {
		if (!token.compare(registers[i]))
			return i+REGISTER_OFFSET;
	}

	// number?
	std::stringstream stoken(token, std::stringstream::in);
	if (!(stoken >> n).fail()) {
		if (n > -32769 && n < 32768) // make sure whole token is a number and range is appropriate
			return n+NUMBER_OFFSET;
	}

	// label?
	if ((n = getlabel(token)) != -1)
		return labels[n].m_line+NUMBER_OFFSET;

	return TOKEN_ERR;
}

int Robot::addlabel(std::string label, int pc, int src_line)
{
	for (int i = 0; i < (int)labels.size(); i++) { // test if label is already defined
		if (!label.compare(labels[i].m_name))
			return 1;
	}
	// label not defined, add it to the table
	labels.push_back(Label(label, pc, src_line));
	return 0;
}

int Robot::getlabel(std::string label)
{
	for (int i = 0; i < (int)labels.size(); i++) {
		if (!label.compare(labels[i].m_name))
			return i;
	}
	return -1;
}

int Robot::clock(void)
{
	if (m_compiled.size() == 0) {
		// not compiled
		return ERR_NOT_COMPILED;
	}

	int ins = m_compiled[m_pc++];

	/*
	std::stack<int> stack = m_stack;
	std::cout << "S: ";
	while (!stack.empty()) {
		std::cout << stack.top() << " ";
		stack.pop();
	}
	std::cout << std::endl << std::endl;
	*/

	if (ins >= OPERATOR_OFFSET && ins < REGISTER_OFFSET) { // operator
		//std::cout << operators[ins - OPERATOR_OFFSET] << std::endl;
		switch (ins - OPERATOR_OFFSET) {
			case 0: { // +
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 + arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 1: { // -
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 - arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 2: { // *
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 * arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 3: { // /
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 / arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 4: { // =
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg1 == arg2) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 5: { // !
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg1 != arg2) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 6: { // >
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 > arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 7: { // <
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 < arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 8: { // abs
				REQUIRES(1);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push(abs(arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 9: { // and
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 && arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 10: { // arccos
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round(acos((double)arg1 / (double)arg2) * 180.0 / M_PI) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 11: { // arcsin
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round(asin((double)arg1 / (double)arg2) * 180.0 / M_PI) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 12: { // arctan
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round((atan2(-arg2, arg1) + M_PI) * 180 / M_PI) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 13: { // beep
				return SUCCESS;
			}
			case 14: { // call
				REQUIRES(1);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push(m_pc + NUMBER_OFFSET);
				m_pc = arg1;
				return SUCCESS;
			}
			case 15: { // chs
				REQUIRES(1);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg1 * -1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 16: { // cosine
				REQUIRES(1);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round(cos(arg1)) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 17: { // debug
				return SUCCESS;
			}
			case 18: { // dist
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round(sqrt((arg1 * arg1) + (arg2 * arg2))) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 19: { // drop
				REQUIRES(1);
				m_stack.pop();
				return SUCCESS;
			}
			case 20: { // dropall
				while (!m_stack.empty())
					m_stack.pop();
				return SUCCESS;
			}
			case 21: { // dup
				REQUIRES(1);
				m_stack.push(m_stack.top());
				return SUCCESS;
			}
			case 22: { // flushint
				return SUCCESS;
			}
			case 23: // icon 0
			case 24: // icon 1
			case 25: // icon 2
			case 26: // icon 3
			case 27: // icon 4
			case 28: // icon 5
			case 29: // icon 6
			case 30: // icon 7
			case 31: // icon 8
			case 32: { // icon 9
				return SUCCESS;
			}
			case 33: { // if
				REQUIRES(2);
				int label = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (m_stack.top() - NUMBER_OFFSET) {
					m_stack.top() = m_pc + NUMBER_OFFSET;
					m_pc = label;
				} else {
					m_stack.pop();
				}
				return SUCCESS;
			}
			case 34: { // ife
				REQUIRES(3);
				int label1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int label2 =m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (m_stack.top() - NUMBER_OFFSET) {
					m_stack.top() = m_pc + NUMBER_OFFSET;
					m_pc = label2;
				} else {
					m_stack.top() = m_pc + NUMBER_OFFSET;
					m_pc = label1;
				}
				return SUCCESS;
			}
			case 35: { // ifg
				REQUIRES(2);
				int label = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (m_stack.top() - NUMBER_OFFSET) {
					m_pc = label;
				}
				m_stack.pop();
				return SUCCESS;
			}
			case 36: { // ifeg
				REQUIRES(3);
				int label1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int label2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (m_stack.top() - NUMBER_OFFSET) {
					m_pc = label2;
				} else {
					m_pc = label1;
				}
				m_stack.pop();
				return SUCCESS;
			}
			case 37: { // intoff
				return SUCCESS;
			}
			case 38: { // inton
				return SUCCESS;
			}
			case 39: { // jump
				 REQUIRES(1);
				 m_pc = m_stack.top() - NUMBER_OFFSET;
				 m_stack.pop();
				 return SUCCESS;
			}
			case 40: { // max
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (arg1 > arg2)
					m_stack.push(arg1);
				else
					m_stack.push(arg2);
				return SUCCESS;
			}
			case 41: { // min
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (arg1 < arg2)
					m_stack.push(arg1);
				else
					m_stack.push(arg2);
				return SUCCESS;
			}
			case 42: { // mod
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((arg2 % arg1) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 43: { // nop
				return SUCCESS;
			}
			case 44: { // not
				REQUIRES(1);
				if (m_stack.top() - NUMBER_OFFSET)
					m_stack.top() = 0 + NUMBER_OFFSET;
				else
					m_stack.top() = 1 + NUMBER_OFFSET;
				return SUCCESS;
			}
			case 45: { // or
				REQUIRES(2);
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (arg1 || arg2)
					m_stack.top() = 1 + NUMBER_OFFSET;
				else
					m_stack.top() = 0 + NUMBER_OFFSET;
				return SUCCESS;
			}
			case 46: { // print
				REQUIRES(1);
				std::cout << m_stack.top() - NUMBER_OFFSET << std::endl;
				return SUCCESS;
			}
			case 47: { // recall
				REQUIRES(1);
				int reg = m_stack.top();
				m_stack.pop();
				if (reg >= REGISTER_OFFSET && reg < REGISTER_MAX) {
					m_stack.push(m_reg[reg - REGISTER_OFFSET] + NUMBER_OFFSET);
					return SUCCESS;
				} else {
					return ERR_REG_RANGE;
				}
			}
			case 48: { // return
				REQUIRES(1);
				m_pc = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				return SUCCESS;
			}
			case 49: { // roll
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				std::stack<int> tmp_stack = m_stack;
				for (int i = 0; i < arg1; i++) {
					tmp_stack.push(m_stack.top());
					m_stack.pop();
				}
				m_stack.push(arg2 + NUMBER_OFFSET);
				for (int i = 0; i < arg1; i++) {
					m_stack.push(tmp_stack.top());
					tmp_stack.pop();
				}
				return SUCCESS;
			}
			case 50: { // rti
				return SUCCESS;
			 }
			case 51: { // setint
				return SUCCESS;
			}
			case 52: { // setparam
				return SUCCESS;
			}
			case 53: { // sine
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round(sin(arg1)) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 54: // snd 0
			case 55: // snd 1
			case 56: // snd 2
			case 57: // snd 3
			case 58: // snd 4
			case 59: // snd 5
			case 60: // snd 6
			case 61: // snd 7
			case 62: // snd 8
			case 63: { // snd 9
				 return SUCCESS;
			}
			case 64: // sto
			case 65: { // store
				REQUIRES(1);
				int reg = m_stack.top();
				m_stack.pop();
				int val = m_stack.top();
				m_stack.pop();
				if (reg >= REGISTER_OFFSET && reg < REGISTER_MAX) {
					//m_reg[reg - REGISTER_OFFSET] = val - NUMBER_OFFSET;
					return set_reg(reg - REGISTER_OFFSET, val - NUMBER_OFFSET);
				} else {
					return ERR_REG_RANGE;
				}
			}
			case 66: { // sqrt
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round(sqrt(arg1)) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 67: { // swap
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push(arg1);
				m_stack.push(arg2);
				return SUCCESS;
			}
			case 68: { // sync
				return SYNC;
			}
			case 69: { // tangent
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				m_stack.push((int)round(tan(arg1)) + NUMBER_OFFSET);
				return SUCCESS;
			}
			case 70: { // vstore
				return SUCCESS;
			}
			case 71: { // vrecall
				return SUCCESS;
			}
			case 72: { // xor
				int arg1 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				int arg2 = m_stack.top() - NUMBER_OFFSET;
				m_stack.pop();
				if (arg1 ^ arg2)
					m_stack.top() = 1 + NUMBER_OFFSET;
				else
					m_stack.top() = 0 + NUMBER_OFFSET;
				return SUCCESS;
			}
		}
	} else if (ins >= REGISTER_OFFSET && ins < REGISTER_MAX) { // register
		//std::cout << registers[ins - REGISTER_OFFSET] << std::endl;
		m_stack.push(ins);
	} else { // number
		//std::cout << ins - NUMBER_OFFSET << std::endl;
		m_stack.push(ins);
	}
	return SUCCESS;
}

int Robot::set_reg(int reg, int val)
{
	if (reg >= 0 && reg <= 25 && reg != X && reg != Y) { // letters
		m_reg[reg] = val;
	} else { // everything else
		switch (reg) {
			case X: break;
			case Y: break;
			case AIM:
				m_reg[AIM] = val % 360;
				if (m_reg[AIM] < 0) m_reg[AIM] += 360;
				break;
			case BOTTOM: break;
			case BULLET:
				if (val > 0) {
					if (val > this->get_energy_max_val()) val = this->get_energy_max_val();
					if (this->get_bullet_type() == 0)
						m_weapons.add_rubber_bullet(m_reg[X], m_reg[Y], m_reg[AIM], this->request_energy(val));
					else
						m_weapons.add_normal_bullet(m_reg[X], m_reg[Y], m_reg[AIM], this->request_energy(val));
				}
				break;
			case CHANNEL:
				if (val < 1 || val > 10) return ERR_INVALID_CHANNEL;
				m_reg[CHANNEL] = val;
				break;
			case CHRONON: break;
			case COLLISION: break;
			case DAMAGE: break;
			case DOPPLER: break;
			case ENERGY: break;
			case FIRE:
				if (val > 0) {
					if (val > this->get_energy_max_val()) val = this->get_energy_max_val();
					switch (this->get_bullet_type()) {
						case 0:
							m_weapons.add_rubber_bullet(m_reg[X], m_reg[Y], m_reg[AIM], this->request_energy(val));
							break;
						case 1:
							m_weapons.add_normal_bullet(m_reg[X], m_reg[Y], m_reg[AIM], this->request_energy(val));
							break;
						case 2:
							m_weapons.add_explosive_bullet(m_reg[X], m_reg[Y], m_reg[AIM], this->request_energy(val));
							break;
					}
				}
				break;
			case FRIEND: break;
			case HISTORY: break;
			case HELLBORE: break; // todo
			case ID: break;
			case KILLS: break;
			case LEFT: break;
			case LOOK: break;
			case MINE: break; // todo
			case MISSILE:
				if (!this->get_missiles()) return ERR_NO_MISSILES;
				if (val > 0) {
					if (val > this->get_energy_max_val()) val = this->get_energy_max_val();
					m_weapons.add_missile(m_reg[X], m_reg[Y], m_reg[AIM], this->request_energy(val));
				}
				break;
			case MOVEX:
				if (val > 0) m_reg[X] += this->request_energy(val)/2;
				else m_reg[X] -= this->request_energy(abs(val))/2;
				break;
			case MOVEY:
				if (val > 0) m_reg[X] += this->request_energy(val)/2;
				else m_reg[X] -= this->request_energy(abs(val))/2;
				break;
			case NUKE: break; // todo
			case PROBE: break;
			case RADAR: break;
			case RANDOM: break; // "!!!!"
			case RANGE: break;
			case RIGHT: break;
			case ROBOTS: break;
			case SCAN:
				m_reg[SCAN] = val % 360;
				if (m_reg[SCAN] < 0) m_reg[SCAN] += 360;
				break;
			case SHIELD:
				if (val > this->get_shield_max_val()) val = this->get_shield_max_val();
				else if (val < 0) val = 0;
				m_reg[SHIELD] += this->request_energy(val - m_reg[SHIELD]);
				break;
			case SIGNAL: break; // todo
			case SPEEDX:
				if (val > 20) val = 20;
				else if (val < -20) val = -20;
				if (val > 0)
					m_reg[SPEEDX] += this->request_energy(abs(val - m_reg[SPEEDX]));
				else
					m_reg[SPEEDX] -= this->request_energy(abs(val - m_reg[SPEEDX]));
				break;
			case SPEEDY:
				if (val > 20) val = 20;
				else if (val < -20) val = -20;
				if (val > 0)
					m_reg[SPEEDY] += this->request_energy(abs(val - m_reg[SPEEDY]));
				else
					m_reg[SPEEDY] -= this->request_energy(abs(val - m_reg[SPEEDY]));
				break;
			case STUNNER: break; // todo
			case TEAMMATES: break;
			case TOP: break;
			case WALL: break;
		}
	}
	return SUCCESS;
}

int RobotState::get_cpu_cycles(void)
{
	// figure out how many cpu cycles we get per chronon
	switch (m_cpu_max) {
		case 0:
			return 5;
		case 1:
			return 10;
		case 2:
			return 15;
		case 3:
			return 30;
		case 4:
			return 50;
		default:
			return 0;
	}
}

int Robot::save(void)
{
	robodrome::save save_data;

	save_data.set_name(m_name);
	save_data.set_src(m_src);

	for (unsigned int i = 0; i < m_compiled.size(); i++) {
		save_data.add_compiled(m_compiled[i]);
	}
	save_data.set_energy_max(m_energy_max);
	save_data.set_damage_max(m_damage_max);
	save_data.set_shield_max(m_shield_max);
	save_data.set_cpu_max(m_cpu_max);
	save_data.set_bullet_type(m_bullet_type);
	save_data.set_missiles(m_missiles);
	save_data.set_tacnukes(m_tacnukes);
	save_data.set_hellbores(m_hellbores);
	save_data.set_mines(m_mines);
	save_data.set_stunners(m_stunners);
	save_data.set_probes(m_probes);
	save_data.set_turret_type(m_turret_type);
	save_data.set_no_negative_energy(m_no_negative_energy);
	for (int i = 0; i < (int)labels.size(); i++) {
		robodrome::save::label *label = save_data.add_ltable();
		label->set_name(labels[i].m_name);
		label->set_line(labels[i].m_line);
		label->set_src_line(labels[i].m_src_line);
	}

	std::fstream output(m_file_path.c_str(), std::ios::out | std::ios::binary);
	if (!save_data.SerializeToOstream(&output))
		return 1;
	return 0;
}

void Robot::open(void)
{
	robodrome::save save_data;
	std::fstream input(m_file_path.c_str(), std::ios::in | std::ios::binary);
	save_data.ParseFromIstream(&input);

	m_name = save_data.name();
	m_src = save_data.src();
	for (int i = 0; i < save_data.compiled_size(); i++) {
		m_compiled.push_back(save_data.compiled(i));
	}
	m_energy_max = save_data.energy_max();
	m_damage_max = save_data.damage_max();
	m_shield_max = save_data.shield_max();
	m_cpu_max = save_data.cpu_max();
	m_bullet_type = save_data.bullet_type();
	m_missiles = save_data.missiles();
	m_tacnukes = save_data.tacnukes();
	m_hellbores = save_data.hellbores();
	m_mines = save_data.mines();
	m_stunners = save_data.stunners();
	m_probes = save_data.probes();
	m_turret_type = save_data.turret_type();
	m_no_negative_energy = save_data.no_negative_energy();
	for (int i = 0; i < save_data.ltable_size(); i++) {
		robodrome::save::label label = save_data.ltable(i);
		addlabel(label.name(), label.line(), label.src_line());
	}
}

int RobotState::hardware_points(void)
{
	return 1 + m_energy_max + m_damage_max + m_shield_max + m_cpu_max + m_bullet_type + m_missiles + m_tacnukes + m_hellbores + m_mines + m_stunners + m_probes;
}

void Robot::init(void)
{
	m_initialized = true;
	m_pc = 0;
	m_alive = true;
	m_killer = "";

	// zero out registers
	for (int i = 0; i < 63; i++) {
		m_reg[i] = 0;
	}
	// empty stack
	while (!m_stack.empty())
		m_stack.pop();

	m_reg[DAMAGE] = this->get_damage_max_val();
	m_reg[ENERGY] = this->get_energy_max_val();
	m_reg[SHIELD] = 0;
	m_weapons.clear();
}

RobotState RobotState::state(void)
{
	return *this;
}

int RobotState::get_cpu_max_val(void)
{
	return cpu_values[m_cpu_max];
}

int RobotState::get_damage_max_val(void)
{
	return damage_values[m_damage_max];
}

int RobotState::get_energy_max_val(void)
{
	return energy_values[m_energy_max];
}

int RobotState::get_shield_max_val(void)
{
	return shield_values[m_shield_max];
}

int Robot::request_energy(int amount)
{
	if (m_no_negative_energy && amount > m_reg[ENERGY]) {
			amount = m_reg[ENERGY];
			m_reg[ENERGY] = 0;
			return amount;
	} else if (amount < 0 && (abs(amount) + m_reg[ENERGY]) > this->get_energy_max_val()) { // handle negative energy requests (this occurs when shield is set to a value lower than its current charge)
		m_reg[ENERGY] = this->get_energy_max_val();
		return amount;
	}
	m_reg[ENERGY] -= amount;
	return amount;
}

// generic point-circle hit detection for the robot-sized circle at x, y
int Weapon::hit(int x, int y)
{
	double xd = m_x - x;
	double yd = m_y - y;
	double dist = xd * xd + yd * yd;
	if (dist <= RADIUSSQUARED) {
		m_done = true;
		return m_damage;
	} else {
		return 0;
	}
}

Rubber_Bullet::Rubber_Bullet(int x, int y, int angle, int energy)
{
	double tmp_x = cos((double)angle * M_PI / 180);
	double tmp_y = sin((double)angle * M_PI / 180);
	m_x = x + ((RADIUS + 1) * tmp_x);
	m_y = y + ((RADIUS + 1) * tmp_y);
	m_x_step = 6 * tmp_x;
	m_y_step = 6 * tmp_y;
	m_done = false;
	m_damage = energy/2;
}

void Rubber_Bullet::step(void)
{
	m_x += m_x_step;
	m_y += m_y_step;
	if (m_x < 0 || m_x > ARENA_WIDTH || m_y < 0 || m_y > ARENA_HEIGHT)
		m_done = true;
}

void Rubber_Bullet::draw(Cairo::RefPtr<Cairo::Context> cr)
{
	cr->save();
	cr->set_source_rgb(0, 0, 1);
	cr->arc(m_x, m_y, 2, 0, 2 * M_PI);
	cr->fill();
	cr->restore();
}

Normal_Bullet::Normal_Bullet(int x, int y, int angle, int energy)
{
	double tmp_x = cos((double)angle * M_PI / 180);
	double tmp_y = sin((double)angle * M_PI / 180);
	m_x = x + ((RADIUS + 1) * tmp_x);
	m_y = y + ((RADIUS + 1) * tmp_y);
	m_x_step = 6 * tmp_x;
	m_y_step = 6 * tmp_y;
	m_done = false;
	m_damage = energy;
}

void Normal_Bullet::step(void)
{
	m_x += m_x_step;
	m_y += m_y_step;
	if (m_x < 0 || m_x > ARENA_WIDTH || m_y < 0 || m_y > ARENA_HEIGHT)
		m_done = true;
}

void Normal_Bullet::draw(Cairo::RefPtr<Cairo::Context> cr)
{
	cr->save();
	cr->set_source_rgb(0, 0, 0);
	cr->arc(m_x, m_y, 2, 0, 2 * M_PI);
	cr->fill();
	cr->restore();
}

Explosive_Bullet::Explosive_Bullet(int x, int y, int angle, int energy)
{
	double tmp_x = cos((double)angle * M_PI / 180);
	double tmp_y = sin((double)angle * M_PI / 180);
	m_x = x + ((RADIUS + 1) * tmp_x);
	m_y = y + ((RADIUS + 1) * tmp_y);
	m_x_step = 6 * tmp_x;
	m_y_step = 6 * tmp_y;
	m_done = false;
	m_damage = energy*1.5;
	m_impact = false;
	m_radius = 0;
}

int Explosive_Bullet::hit(int x, int y)
{
	double xd = m_x - x;
	double yd = m_y - y;
	double dist = xd * xd + yd * yd;
	if (dist <= (10 + m_radius) * (10 + m_radius)) {
		if (m_radius == 0 && m_impact) {
			return m_damage;
		}
		m_impact = true;
	}
	return 0;
}

void Explosive_Bullet::step(void)
{
	if (m_radius == 36) {
		m_done = true;
		m_radius = 0; // pop!
		play_snd[NUKE_SND] = true;
		return;
	}
	if (m_impact) {
		m_radius += 4.5;
	} else {
		m_x += m_x_step;
		m_y += m_y_step;
		if (m_x < 0 || m_x > ARENA_WIDTH || m_y < 0 || m_y > ARENA_HEIGHT)
			m_done = true;
	}
}

void Explosive_Bullet::draw(Cairo::RefPtr<Cairo::Context> cr)
{
	cr->save();
	if (m_impact) {
		cr->set_source_rgba(1, 0, 0, 0.5);
		cr->arc(m_x, m_y, m_radius, 0, 2 * M_PI);
		cr->fill();
	} else {
		cr->set_source_rgb(1, 0, 0);
		cr->arc(m_x, m_y, 2, 0, 2 * M_PI);
		cr->fill();
	}
	cr->restore();
}

Missile::Missile(int x, int y, int angle, int energy)
{
	double tmp_x = cos((double)angle * M_PI / 180);
	double tmp_y = sin((double)angle * M_PI / 180);
	m_x = x + ((RADIUS + 1)* tmp_x);
	m_y = y + ((RADIUS + 1)* tmp_y);
	m_x_step = 2.5 * tmp_x;
	m_y_step = 2.5 * tmp_y;
	m_done = false;
	m_damage = energy*2;
}

void Missile::step(void)
{
	m_x += m_x_step;
	m_y += m_y_step;
	if (m_x < 0 || m_x > ARENA_WIDTH || m_y < 0 || m_y > ARENA_HEIGHT)
		m_done = true;
}

void Missile::draw(Cairo::RefPtr<Cairo::Context> cr)
{
	cr->save();
	cr->set_line_width(1);
	cr->set_source_rgb(0, 0, 0);
	cr->move_to(m_x, m_y);
	cr->line_to(m_x - m_x_step, m_y - m_y_step);
	cr->stroke();
	cr->restore();
}

void WeaponList::add_rubber_bullet(int x, int y, int angle, int energy)
{
	m_weapons.push_back(new Rubber_Bullet(x, y, angle, energy));
	play_snd[GUN_SND] = true;
}

void WeaponList::add_normal_bullet(int x, int y, int angle, int energy)
{
	m_weapons.push_back(new Normal_Bullet(x, y, angle, energy));
	play_snd[GUN_SND] = true;
}

void WeaponList::add_explosive_bullet(int x, int y, int angle, int energy)
{
	m_weapons.push_back(new Explosive_Bullet(x, y, angle, energy));
	play_snd[GUN_SND] = true;
}

void WeaponList::add_missile(int x, int y, int angle, int energy)
{
	m_weapons.push_back(new Missile(x, y, angle, energy));
	play_snd[MISSILE_SND] = true;
}

void WeaponList::step(void)
{
	// destroy weapons marked as 'done'
	for (int i=0; i < (int)m_weapons.size();) {
		if (m_weapons.at(i)->get_done()) {
			m_weapons.erase(m_weapons.begin() + i);
		} else {
			i++;
		}
	}

	for (int i=0; i < (int)m_weapons.size(); i++) {
		m_weapons.at(i)->step();
	}
}

int WeaponList::hit(int x, int y)
{
	int damage = 0;

	for (int i=0; i < (int)m_weapons.size(); i++) {
		damage += m_weapons.at(i)->hit(x, y);
	}
	return damage;
}

void WeaponList::draw(Cairo::RefPtr<Cairo::Context> cr)
{
	for (int i=0; i < (int)m_weapons.size(); i++) {
		m_weapons.at(i)->draw(cr);
	}
}
